package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.constants.EsConstants;
import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient client;

    /**
     * 得到列表
     *
     * @param requestParams 请求参数
     * @return {@code PageResult}
     */
    @Override
    public PageResult getList(RequestParams requestParams) {
        try {
            //获取请求参数
            Integer page = requestParams.getPage();
            Integer size = requestParams.getSize();

            //创建SearchRequest对象
            SearchRequest searchRequest = new SearchRequest(EsConstants.HOTEL);

            BoolQueryBuilder boolQuery = searchCondition(requestParams, searchRequest);

            //广告排位
            searchRequest.source().query(
                    QueryBuilders.functionScoreQuery(
                            boolQuery,
                            new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                            QueryBuilders.termQuery("isAD", true)
                                            ,
                                            ScoreFunctionBuilders.weightFactorFunction(100)
                                    )
                            }
                    ).boostMode(CombineFunction.SUM)
            );

            //排序
            String sortBy = requestParams.getSortBy();
            if ("price" .equals(sortBy) ){
                //根据价格升序排列
                searchRequest.source().sort(sortBy,SortOrder.ASC);
            }else if ("score" .equals(sortBy) ){
                //根据评价降序排列
                searchRequest.source().sort(sortBy,SortOrder.DESC);
            }
                //距离排序
                String location = requestParams.getLocation();
            if (StringUtils.isNotBlank(location)) {
                searchRequest.source().
                        sort(SortBuilders
                                .geoDistanceSort("location", new GeoPoint(location))
                                .order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS));
            }

            //分页
            searchRequest.source().from((page - 1) * size).size(size);
            //执行搜索
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            long totalValue = searchResponse.getHits().getTotalHits().value;

            List<HotelDoc> hotels = new ArrayList<>();
            SearchHit[] searchHits = searchResponse.getHits().getHits();
            for (SearchHit searchHit : searchHits) {
                String json = searchHit.getSourceAsString();
                HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
                //获取距离值
                Object[] sortValues = searchHit.getSortValues();
                if (ObjectUtils.isNotEmpty(sortValues)) {
                    Object distance = sortValues[0];
                    if (distance != null) {
                        //封装距离
                        hotelDoc.setDistance(distance);
                    }
                }
                hotels.add(hotelDoc);
            }

            //构建返回集合
            return new PageResult(totalValue, hotels);
        } catch (IOException e) {
            throw new RuntimeException();
        }
    }

    /**
     * 过滤器
     *
     * @param requestParams 请求参数
     * @return {@code Map<String, List<String>>}
     */
    @Override
    public Map<String, List<String>> filters(RequestParams requestParams) {

        //创建SearchRequest对象
        SearchRequest searchRequest = new SearchRequest(EsConstants.HOTEL);
        //查询条件
        searchCondition(requestParams, searchRequest);
        //封装返回结果
        List<String> cities = getByAggName(searchRequest, "cityAgg", "city");
        List<String> brands = getByAggName(searchRequest, "brandAgg", "brand");
        List<String> starNames = getByAggName(searchRequest, "starNameAgg", "starName");

        Map<String, List<String>> map = new HashMap<>();
        map.put("city", cities);
        map.put("brand", brands);
        map.put("starName", starNames);
        return map;
    }

    @Override
    public List<String> getSuggestion(String key) {
        List<String> suggests = new ArrayList<>();
        try {
            SearchRequest searchRequest = new SearchRequest(EsConstants.HOTEL);
            searchRequest.source().suggest(new SuggestBuilder().addSuggestion(
                    "hotel_suggest",
                    SuggestBuilders.completionSuggestion("suggestion")
                            .prefix(key)
                            .skipDuplicates(true)
                            .size(10)
            ));
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            // 根据补全名称查询，获取查询结果
            CompletionSuggestion completionSuggestion =
                    searchResponse.getSuggest().getSuggestion("hotel_suggest");
            // 获取Options
            List<CompletionSuggestion.Entry.Option> options = completionSuggestion.getOptions();
            //遍历
            for (CompletionSuggestion.Entry.Option option : options) {
                String str = option.getText().toString();
                suggests.add(str);
            }
            //返回结果
            return suggests;
        } catch (IOException e) {
            throw new RuntimeException();
        }
    }


    /**
     * 通过字段名称得到结果  返回list集合
     *
     * @param searchRequest 搜索请求
     * @param aggName       聚合名称
     * @param fieldName     字段名称
     * @return {@code List<String>}
     */
    private List<String> getByAggName(SearchRequest searchRequest, String aggName, String fieldName) {
        List<String> arrs = new ArrayList<>();
        try {
            searchRequest.source().size(0);     //不显示查询结果记录
            searchRequest.source().aggregation(
                    AggregationBuilders
                            .terms(aggName)   //聚合类型名称
                            .field(fieldName)           //聚合属性名称
                            .size(100)                //聚合数据
            );
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            // 处理返回结果
            Terms terms = searchResponse.getAggregations().get(aggName);
            List<? extends Terms.Bucket> buckets = terms.getBuckets();
            for (Terms.Bucket bucket : buckets) {
                String keyAsString = bucket.getKeyAsString();
                arrs.add(keyAsString);
            }
        } catch (IOException e) {
            throw new RuntimeException();
        }
        return arrs;
    }

    /**
     * 根据条件搜索
     *
     * @param requestParams 请求参数
     * @param searchRequest 搜索请求
     * @return {@code BoolQueryBuilder}
     */
    private BoolQueryBuilder searchCondition(RequestParams requestParams, SearchRequest searchRequest) {
        //====================搜索条件=========================
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //1.1 搜索框使用must 判断key是否存在
        String key = requestParams.getKey();
        if (StringUtils.isBlank(key)) {
            //查询所有
            boolQuery.must(QueryBuilders.matchAllQuery());
        } else {
            //根据key查询
            boolQuery.must(QueryBuilders.matchQuery("name", key));
        }

        //1.2 brand term
        String brand = requestParams.getBrand();
        if (StringUtils.isNotBlank(brand)) {
            boolQuery.filter(QueryBuilders.termQuery("brand", brand));
        }
        //1.2 starName term
        String starName = requestParams.getStarName();
        if (StringUtils.isNotBlank(starName)) {
            boolQuery.filter(QueryBuilders.termQuery("starName", starName));
        }
        //1.2 city term
        String city = requestParams.getCity();
        if (StringUtils.isNotBlank(city)) {
            boolQuery.filter(QueryBuilders.termQuery("city", city));
        }
        //1.2 minPrice maxPrice range
        Integer minPrice = requestParams.getMinPrice();
        Integer maxPrice = requestParams.getMaxPrice();
        if (ObjectUtils.isNotEmpty(minPrice) && ObjectUtils.isNotEmpty(maxPrice)) {
            boolQuery.filter(QueryBuilders.rangeQuery("price")
                    .gte(minPrice)
                    .lte(maxPrice));
        }
        searchRequest.source().query(boolQuery);
        //=============================================
        return boolQuery;
    }
}
